﻿using System;
using System.Collections.Generic;
using System.Data.Entity.Design.PluralizationServices;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using EnvDTE;
using Flamefusion.TodocPackage;
using Microsoft.VisualStudio.Shell;

namespace Flamefusion.Todoc
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    ///
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the 
    /// IVsPackage interface and uses the registration attributes defined in the framework to 
    /// register itself and its components with the shell.
    /// </summary>
    // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
    // a package.
    [PackageRegistration(UseManagedResourcesOnly = true)]
    // This attribute is used to register the information needed to show this package
    // in the Help/About dialog of Visual Studio.
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    [Guid(GuidList.guidTodocPackagePkgString)]
    public sealed class TodocPackage : Package
    {
        private readonly PluralizationService pluralizationService = PluralizationService.CreateService(new CultureInfo("en-US"));

        /// <summary>
        /// Default constructor of the package.
        /// Inside this method you can place any initialization code that does not require 
        /// any Visual Studio service because at this point the package object is created but 
        /// not sited yet inside Visual Studio environment. The place to do all the other 
        /// initialization is the Initialize method.
        /// </summary>
        public TodocPackage()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));

        }

        /// <summary>Gets a sentence.</summary>
        /// <param term="name">The name of the Sentence.</param>
        private string GetSentence(string name)
        {
            string result = string.Empty;
            foreach (var word in GetWords(name))
            {
                result += word.ToLower() + " ";
            }
            result = result.Substring(0, result.Length - 1);
            return result;
        }

        /// <summary>Gets some words.</summary>
        private string[] GetWords(string name)
        {
            var words = new List<string>();

            var wasUppercase = true;

            var word = string.Empty;

            foreach (var character in name)
            {

                if (char.IsUpper(character))
                {
                    if (!wasUppercase)
                    {

                        words.Add(word);
                        word = string.Empty;

                        wasUppercase = true;

                    }
                }
                else
                {

                    wasUppercase = false;

                }

                word += character;
            }

            if (!string.IsNullOrEmpty(word))
            {
                words.Add(word);
            }

            return words.ToArray();

        }

        /// <summary>Adds a comment.</summary>
        private void AddComment(CodeProperty codeFunction)
        {
        }

        /// <summary>Adds a comment.</summary>
        private void AddComment(CodeFunction codeFunction)
        {
            //TODO: find a better way of clearing default comment garbage. perhaps an XML parser?
            var defaultComment = codeFunction.DocComment.Replace("<doc>", "").Replace("</doc>", "").Replace("\r", "").Replace("\n", "");

            if (string.IsNullOrEmpty(defaultComment))
            {

                var words = GetWords(codeFunction.Name);

                if (words.Length == 2)
                {
                    var key = string.Empty;

                    var verb = pluralizationService.IsSingular(words[0]) ? words[0] : pluralizationService.Singularize(words[0]);
                    var noun = words[1];

                    var arguments = string.Empty;

                    if (pluralizationService.IsPlural(noun))
                    {

                        key = "some";

                        foreach (CodeParameter parameter in codeFunction.Parameters)
                        {
                            arguments += "\r\n<param term='" + parameter.Name + "'>The " + GetSentence(parameter.Name) + " for the " + noun.ToLower() + " to " + verb.ToLower() + ".</param>";
                        }

                    }
                    else
                    {

                        if (noun[0] == 'A' || noun[0] == 'E' || noun[0] == 'I' || noun[0] == 'O' || noun[0] == 'U' || noun[0] == 'Y')
                        {
                            key = "an";
                        }
                        else
                        {
                            key = "a";
                        }

                        foreach (CodeParameter parameter in codeFunction.Parameters)
                        {
                            arguments += "\r\n<param term='" + parameter.Name + "'>The " + GetSentence(parameter.Name) + " of the " + noun.ToLower() + " to " + verb.ToLower() + ".</param>";
                        }

                    }

                    codeFunction.DocComment = "<doc><summary>" + pluralizationService.Pluralize(verb) + " " + key + " " + noun.ToLower() + ".</summary>" + arguments + "</doc>";

                }
            }
        }

        void ProcessCodeElements(CodeElements elements)
        {

            //TODO: i think a stackoverflow exception could occur here. we should probably make this linear instead of recursive.

            foreach (CodeElement codeElement in elements)
            {

                try
                {

                    //MessageBox.Show("Code element found: " + codeElement.Name + "\nIs code type: " + codeElement.IsCodeType);

                    if (codeElement.IsCodeType && codeElement.Kind != vsCMElement.vsCMElementDelegate)
                    {

                        var codeType = codeElement as CodeType;

                        ProcessCodeElements(codeType.Members);
                    }
                    else if (codeElement.Kind == vsCMElement.vsCMElementNamespace)
                    {

                        var codeNamespace = codeElement as CodeNamespace;

                        ProcessCodeElements(codeNamespace.Members);

                    }
                    else if (codeElement.Kind == vsCMElement.vsCMElementFunction)
                    {

                        //MessageBox.Show("Function found!");

                        AddComment(codeElement as CodeFunction);

                    }
                    else if (codeElement.Kind == vsCMElement.vsCMElementProperty)
                    {

                        //MessageBox.Show("Property found!");

                        AddComment(codeElement as CodeProperty);

                    }
                }
                catch (COMException)
                {
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString());
                }

            }
        }

        void DocumentEvents_DocumentSaved(Document Document)
        {
            var projectItem = Document.ProjectItem;

            //HACK: not nescessarily a good way of checking whether or not it's code that's being executed.
            var fileCodeModel = projectItem.FileCodeModel;
            ProcessCodeElements(fileCodeModel.CodeElements);
        }

        /////////////////////////////////////////////////////////////////////////////
        // Overridden Package Implementation
        #region Package Members

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initialization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();

            var dte2 = (EnvDTE80.DTE2)Marshal.GetActiveObject("VisualStudio.DTE.10.0");

            var events = dte2.Events.DocumentEvents;
            events.DocumentSaved += DocumentEvents_DocumentSaved;
            events.DocumentOpened += DocumentEvents_DocumentSaved;

        }
        #endregion

    }
}
